<?php

/*
 * этапы работы
 * 1. сохранение/получение промежуточного рез-та в сущность
 * 2. фильтрация
 * 3. валидация
 * 4. визуализация
 * 5. вернуть значение
 */

namespace Pinenut;

use Pinenut\Request;
use Pinenut\Form\Model;
use Pinenut\Form\Element;

class Form
{

	protected $defaultFormRender = 'forms.render.form';
	protected $formModel = null;

	protected $sessionId;

	protected $formId;

	/**
	 * @var \Pinenut\Form\Element[]
	 */
	protected $elements;

//	protected $formConfig;

	protected $partial = false;


	/**
	 * Если установлен, то значит что запись по форме только создана и нельзя
	 * принимать данные, т.к. это csrf
	 * @var boolean
	 */
	protected $isNewModel = false;



	/**
	 * Проверочный код формы
	 * @var string
	 */
	public $code;

	/**
	 * Стиль формы, как стиль для шаблона
	 * @var unknown
	 */
	public $style;

	/**
	 * Параметры
	 * @var array
	 */
	public $params = [];

	/**
	 * Инициируем форму
	 *
	 * @param unknown $sessionId
	 * @param unknown $formId
	 * @param unknown $formConfig
	 * @param unknown $data
	 */
	public function __construct($sessionId, $formId, $formConfig, $data = array())
	{
		// сохраняем контекстные данные
		$this->sessionId = $sessionId;
		$this->formId = $formId;

		// разные там параметры из конфы
		$this->style = isset($formConfig['style']) ? $formConfig['style'] : null;

		// конфу todo: нафиг
	//	$this->formConfig = $formConfig;

		// создаю массив элементов по конфе
		foreach ($formConfig['elements'] as $elementName => $elementConfig) {
			$elementConfig['name'] = $elementName;
			$this->elements[$elementName] = new Element($elementConfig);
		}
		unset($formConfig['elements']);
		$this->params = $formConfig;

		// населяю данными из модели-черновика
		$formModel = $this->getModel();

		// если у нас новая форма - нужно получить все пустые поля и отфильровать их
		if ($this->isNewModel) {
			$values = $this->getValues();
			$this->setValues($values);
			$this->filter($values);
		}

		$this->setValues($formModel->formFields);
		$this->setMasterValues();
	}

	/**
	 * Возвращаем модель-черновик
	 * Или находим, или создаём
	 *
	 * @return \Pinenut\Form\Model
	 */
	protected function getModel()
	{
		if (null === $this->formModel) {
			$this->formModel = Model::find()->active(true)->where('formId', $this->getFullFormId())->one();

			if (null === $this->formModel) {
				$this->formModel = new Model();
				$this->formModel->formId = $this->getFullFormId();
				$this->formModel->code = $this->generateCode();
				$this->isNewModel = true;
				$this->formModel->save();
			}
		}

		return $this->formModel;
	}

	function generateCode() {
		return md5(md5(time()).time());
		//return md5(md5(time()).$this->getFullFormId());
	}

	function getCode() {
		return $this->getModel()->code;
	}

	protected function getFullFormId()
	{
		return $this->sessionId . '.' . $this->formId;
	}

	public function __toString()
	{
		try {
			return $this->render();
		} catch (\Exception $e) {
			return 'Form error'.(\Application::isDebug()?': '.$e->getMessage():'!');
		}

	}

	/**
	 * Хелпер для вызова действия в контексте текущего
	 *
	 * @param string $action имя действия
	 * @param array $params	параметры вызова
	 * @param string|null $view имя шаблона для этого действия
	 * @return \Pinenut\Response
	 */
	function action($action, array $params = array(), $view = null, $deferred = true)
	{
		$response = \Dispatcher::action(new Request($action, $params, $view, $deferred));
		return $response;
	}

	public function render($elementsList = null)
	{
		return $this->action($this->defaultFormRender, $this->toArray(), $this->getStyle())->__toString();
	}

	public function toArray() {
		$params = [];
		$params['elements'] = $this->elements;
		$params['partial'] = $this->partial;
		$params['code'] = $this->getCode();
		$params['params'] = $this->params;
		return $params;
	}

	/**
	 * Установить дополнительный параметр (прокинуть в форму)
	 *
	 * @param string $key
	 * @param mixed $value
	 */
	public function setParam($key, $value) {
		$this->params[$key] = $value;
	}

	/**
	 * Получить значение параметра
	 *
	 * @param string $key
	 */
	public function getParam($key) {
		return array_key_exists($key, $this->params) ? $this->params[$key] : null;
	}

	public function getRenderValues()
	{
		$values = [];
		foreach ($this->elements as $name=>$element) {
			$data = $element->render(null, true);
			$values[$name] = $data->value;
		}
		return $values;
	}

	public function getStyle()
	{
		return $this->style;
	}

// 	public function setElements($elementsConfig)
// 	{
// 		$this->formConfig['elements'] = $elementsConfig;
// 	}

// 	public function __get($name)
// 	{
// 		if ('elements' == $name) {
// 			if (empty($this->formConfig['elements'])) {
// 				$this->formConfig['elements'];
// 			}
// 			return $this->formConfig['elements'];

// 		} else if (!empty($this->formConfig['elements'][$name]['value']))  {
// 			return $this->formConfig['elements'][$name]['value'];
// 		}
// 	}

// 	public function __set($name, $value)
// 	{
// 		if ('elements' == $name) {
// 			$this->formConfig['elements'] = $value;
// 		} else if (!empty($this->formConfig['elements'][$name]))  {
// 			$this->formConfig['elements'][$name]['value'] = $value;
// 		}
// 	}

	public function process($fieldsData, $partial = false, $internal = false)
	{
		/**
		 * 1. ищем данные по форме, если не находим, то массив дефолтных данных и накладываем пришедший массив данных,
		 * сохраняем данные
		 * 2. фильтруем весь массив
		 * 3. для подчинёных полей получаем данные мастер полей
		 * 4. валидация
		 */
		// если процессим только что проинициированную модель - то это csrf
		if (!$internal && ($this->isNewModel || @$fieldsData['___'] != $this->formModel->code)) {
			throw new \Exception('Invalid form', 400);
		}

		$this->partial = $partial;

		// 1. Перезаписываю старые значения новыми (они как раз только что пришли)
		$this->setValues($fieldsData);
		// 2. Сохраняю сырые данные формы
		$this->filter($fieldsData);
		$this->save();
		return $this->validate();
	}

	function setValues($fieldsData) {

		// сохраняем код для проверки
//		$this->code = @$fieldsData['___'];

		// распихиваем значения
		if (is_array($fieldsData)) foreach($fieldsData as $name => $value) {
			if (isset($this->elements[$name])) {
				$this->elements[$name]->setValue($value);
				$this->elements[$name]->isRender = $this->partial;
			}
		}
	}

	public function setMasterValues() {
		
		foreach ($this->elements as $element) {
			/* @var $element \Pinenut\Form\Element */
			if ($name = ($element->master?$element->master:$element->getMasterValue)) {
				$element->setMasterValue($this->elements[$name]->getValue());
			}
		}
	}

	public function save()
	{
		$formModel = $this->getModel();
		foreach ($this->elements as $elementName => $element) {
			$formModel->formFields[$elementName] = $element->getValue();
		}
		$formModel->save();
	}

	public function flush($onlyData = false)
	{
        if (!$onlyData) {
        	$this->getModel()->remove();
            $this->formModel = null;
        } else {
            $formModel = $this->getModel();
            $formModel->formFields = null;
            $formModel->save();
        }
	}

	public function filter($fieldsData)
	{
		$fields = array_keys($fieldsData);
		foreach ($this->elements as $name => $element) {
			if (in_array($name, $fields))
				$element->filter();
		}
	}

	public function validate()
	{
		
		$this->setMasterValues();
		
		$isValid = true;

		foreach ($this->elements as $name => $element) {
			if (!$element->validate()) {
				$isValid = false;
			}
		}
		
		return $isValid;
	}

// 	/**
// 	 * Населяем форму из модели формы
// 	 *
// 	 * @param Model $formModel
// 	 */
// 	public function populate(Model $formModel)
// 	{
// 		foreach((array)$formModel->formFields as $name => $value) {
// 			if (!empty($this->formConfig['elements'][$name])) {
// 				$this->formConfig['elements'][$name]['value'] = $value;
// 			}
// 		}
// 	}

	public function getValues()
	{
		$retval = array();
		foreach ($this->elements as $elementName => $element) {
			$retval[$elementName] = $element->getValue();
		}
		return $retval;
	}

	public function addErrors($element, $errors) {
		$this->elements[$element]->addError($errors);
	}
}